/* 
 * Copyright (C) 2008 Torgny Bjers
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sugree.jibjib.net;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.zip.GZIPInputStream;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpMessage;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpParams;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.SharedPreferences;
import android.location.Location;
import android.util.Log;

import oauth.signpost.OAuthConsumer;
import oauth.signpost.http.HttpParameters;
import oauth.signpost.signature.QueryStringSigningStrategy;
import oauth.signpost.signature.AuthorizationHeaderSigningStrategy;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;

import com.sugree.jibjib.PrivateData;
import com.sugree.jibjib.util.Base64;

/**
 * Handles connection to the Twitter REST API.
 * 
 * @author torgny.bjers
 */
public class Connection {

	private static final String JSON = "json";
	private static final String XML = "xml";

	private static final String EXTENSION = ".json";

	private static final String STATUSES_PUBLIC_TIMELINE_URL = "/statuses/public_timeline" + EXTENSION;
	private static final String STATUSES_HOME_TIMELINE_URL = "/statuses/home_timeline" + EXTENSION;
	private static final String STATUSES_FRIENDS_TIMELINE_URL = "/statuses/friends_timeline" + EXTENSION;
	private static final String STATUSES_MENTIONS_TIMELINE_URL = "/statuses/mentions" + EXTENSION;
	private static final String STATUSES_FOLLOWERS_URL = "/statuses/followers" + EXTENSION;
	private static final String STATUSES_UPDATE_URL = "/statuses/update" + EXTENSION;
	private static final String STATUSES_SHOW_URL = "/statuses/show" + EXTENSION;
	private static final String STATUSES_DESTROY_URL = "/statuses/destroy/";
	private static final String STATUSES_RETWEET_URL = "/statuses/retweet/";
	private static final String DIRECT_MESSAGES_URL = "/direct_messages" + EXTENSION;
	private static final String DIRECT_MESSAGES_SENT_URL = "/direct_messages/sent" + EXTENSION;
	private static final String ACCOUNT_VERIFY_CREDENTIALS_URL = "/account/verify_credentials" + EXTENSION;
	private static final String ACCOUNT_VERIFY_CREDENTIALS = "/account/verify_credentials.";
	private static final String ACCOUNT_RATE_LIMIT_STATUS_URL = "/account/rate_limit_status" + EXTENSION;
	private static final String FAVORITES_CREATE_URL = "/favorites/create/";
	private static final String FAVORITES_DESTROY_URL = "/favorites/destroy/";
	private static final String USER_AGENT = "jibjib/1.0";
	private static final String SOURCE_PARAMETER = "jibjib";
	private static final String TAG = "JibJibConnection";

	private static final String GATEWAY_TWITTER_URL = "http://api.twitter.com/1";
	private static final String GATEWAY_PICTURE_URL = "http://twitpic.com";
	private static final String TWITPIC_PICTURE_URL = "";
	private static final String TWEETPHOTO_PICTURE_URL = "/api/upload.aspx";

	private static final Integer DEFAULT_CONNECTION_TIMEOUT = 60000;
	private static final Integer DEFAULT_SO_TIMEOUT = 120000;
	private static final Integer BUFFER_SIZE = 2048;

	private String mUsername;
	private String mPassword;
	private long mSinceId;
	private int mLimit = 200;
	private String mTwitterGateway = GATEWAY_TWITTER_URL;
	private String mPictureGateway = GATEWAY_PICTURE_URL;
	private boolean mUseOAuth = false;
	private String mOAuthAccessToken;
	private String mOAuthTokenSecret;
	private OAuthConsumer mOAuthConsumer;
	private OAuthConsumer mOAuthEcho;

	private final DateFormat mDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);

	/**
	 * Creates a new Connection instance.
	 * 
	 * Requires a user name and password.
	 * 
	 * @param username
	 * @param password
	 */
	public Connection(String username, String password) {
		mUsername = username;
		mPassword = password;
	}

	/**
	 * Creates a new Connection instance, specifying a last ID.
	 * 
	 * Requires a user name and password as well as a last run time.
	 * 
	 * @param username
	 * @param password
	 * @param lastId
	 */
	public Connection(String username, String password, long sinceId) {
		mUsername = username;
		mPassword = password;
		mSinceId = sinceId;
	}

	/**
	 * Creates a new Connection instance, specifying a last ID.
	 * 
	 * Requires a user name and password as well as a last run time.
	 * 
	 * @param username
	 * @param password
	 * @param lastId
	 */
	public Connection(String username, String password, long sinceId, int limit) {
		mUsername = username;
		mPassword = password;
		mSinceId = sinceId;
		mLimit = limit;
		if (mLimit < 1) mLimit = 1;
		if (mLimit > 200) mLimit = 200;
	}

	public Connection(SharedPreferences sp) {
		mUsername = sp.getString("twitter_username", null);
		mPassword = sp.getString("twitter_password", null);
		mTwitterGateway = sp.getString("twitter_gateway", GATEWAY_TWITTER_URL);
		mPictureGateway = sp.getString("picture_gateway", GATEWAY_PICTURE_URL);
		mUseOAuth = sp.getBoolean("use_oauth", false);
		mOAuthAccessToken = sp.getString("oauth_access_token", null);
		mOAuthTokenSecret = sp.getString("oauth_token_secret", null);

		if (mUseOAuth) {
			mOAuthConsumer = new CommonsHttpOAuthConsumer(PrivateData.OAUTH_CONSUMER_KEY, PrivateData.OAUTH_CONSUMER_SECRET);
			mOAuthConsumer.setTokenWithSecret(mOAuthAccessToken, mOAuthTokenSecret);
			mOAuthEcho = new CommonsHttpOAuthConsumer(PrivateData.OAUTH_CONSUMER_KEY, PrivateData.OAUTH_CONSUMER_SECRET);
			mOAuthEcho.setTokenWithSecret(mOAuthAccessToken, mOAuthTokenSecret);
			/*
			Log.d(TAG, "oauth "+mOAuthAccessToken+" "+mOAuthTokenSecret);
			*/
		}
	}

	public void setSinceId(long sinceId) {
		mSinceId = sinceId;
	}

	public void setLimit(int limit) {
		mLimit = limit;
	}

	public void setTwitterGateway(String url) {
		if (url == null) {
			url = GATEWAY_TWITTER_URL;
		}
		mTwitterGateway = url;
	}

	public void setPictureGateway(String url) {
		if (url == null) {
			url = GATEWAY_PICTURE_URL;
		}
		mPictureGateway = url;
	}

	/**
	 * Get the user's public timeline.
	 * 
	 * Returns the 20 most recent statuses from non-protected users who have set
	 * a custom user icon. Does not require authentication. Note that the public
	 * timeline is cached for 60 seconds so requesting it more often than that
	 * is a waste of resources.
	 * 
	 * @return JSONArray
	 * @throws JSONException
	 * @throws ConnectionException 
	 * @throws ConnectionUnavailableException 
	 * @throws ConnectionAuthenticationException 
	 * @throws SocketTimeoutException 
	 */
	public JSONArray getPublicTimeline() throws JSONException, ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		return new JSONArray(getRequest(STATUSES_PUBLIC_TIMELINE_URL));
	}

	/**
	 * Get the user's own and friends timeline.
	 * 
	 * Returns the 100 most recent statuses posted by the authenticating user and
	 * that user's friends. This is the equivalent of /home on the Web.
	 * 
	 * @return JSONArray
	 * @throws ConnectionException 
	 * @throws ConnectionAuthenticationException 
	 * @throws ConnectionUnavailableException 
	 * @throws SocketTimeoutException 
	 */
	public JSONArray getFriendsTimeline() throws ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		String url = STATUSES_HOME_TIMELINE_URL;
		url += "?count=" + mLimit;
		if (mSinceId > 0) {
			url += "&since_id=" + mSinceId;
		}
		JSONArray jArr = null;
		String request = getRequest(url);
		try {
			jArr = new JSONArray(request);
		} catch (JSONException e) {
			try {
				JSONObject jObj = new JSONObject(request);
				String error = jObj.optString("error");
				if ("Could not authenticate you.".equals(error)) {
					throw new ConnectionAuthenticationException(error);
				}
			} catch (JSONException e1) {
				throw new ConnectionException(e);
			}
		}
		return jArr;
	}

	/**
	 * Get the user's replies.
	 * 
	 * Returns the 20 most recent @replies (status updates prefixed with @username) 
	 * for the authenticating user.
	 * 
	 * @return JSONArray
	 * @throws ConnectionException 
	 * @throws ConnectionAuthenticationException 
	 * @throws ConnectionUnavailableException 
	 * @throws SocketTimeoutException 
	 */
	public JSONArray getMentionsTimeline() throws ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		String url = STATUSES_MENTIONS_TIMELINE_URL;
		url += "?count=" + mLimit;
		if (mSinceId > 0) {
			url += "&since_id=" + mSinceId;
		}
		JSONArray jArr = null;
		String request = getRequest(url);
		try {
			jArr = new JSONArray(request);
		} catch (JSONException e) {
			try {
				JSONObject jObj = new JSONObject(request);
				String error = jObj.optString("error");
				if ("Could not authenticate you.".equals(error)) {
					throw new ConnectionAuthenticationException(error);
				}
			} catch (JSONException e1) {
				throw new ConnectionException(e);
			}
		}
		return jArr;
	}

	public JSONObject getStatus(long id) throws ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		String url = STATUSES_SHOW_URL+"?id="+id;
		JSONObject jObj = null;
		String request = getRequest(url);
		try {
			jObj = new JSONObject(request);
		} catch (JSONException e) {
			try {
				jObj = new JSONObject(request);
				String error = jObj.optString("error");
				if ("Could not authenticate you.".equals(error)) {
					throw new ConnectionAuthenticationException(error);
				}
			} catch (JSONException e1) {
				throw new ConnectionException(e);
			}
		}
		return jObj;
	}

	/**
	 * Get the user's followers from the Twitter REST API.
	 * 
	 * @return JSONArray
	 * @throws ConnectionException
	 * @throws ConnectionAuthenticationException
	 * @throws ConnectionUnavailableException
	 * @throws SocketTimeoutException
	 */
	public JSONArray getFollowers() throws ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		String url = STATUSES_FOLLOWERS_URL;
		JSONArray jArr = null;
		String request = getRequest(url);
		try {
			jArr = new JSONArray(request);
		} catch (JSONException e) {
			try {
				JSONObject jObj = new JSONObject(request);
				String error = jObj.optString("error");
				if ("Could not authenticate you.".equals(error)) {
					throw new ConnectionAuthenticationException(error);
				}
			} catch (JSONException e1) {
				throw new ConnectionException(e);
			}
		}
		return jArr;
	}

	/**
	 * Get the user's own and friends timeline.
	 * 
	 * Returns the 100 most recent direct messages for the authenticating user.
	 * 
	 * @return JSONArray
	 * @throws ConnectionException 
	 * @throws ConnectionAuthenticationException 
	 * @throws ConnectionUnavailableException 
	 * @throws SocketTimeoutException 
	 */
	public JSONArray getDirectMessages() throws ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		String url = DIRECT_MESSAGES_URL;
		url += "?count=" + mLimit;
		if (mSinceId > 0) {
			url += "&since_id=" + mSinceId;
		}
		JSONArray jArr = null;
		String request = getRequest(url);
		try {
			jArr = new JSONArray(request);
		} catch (JSONException e) {
			try {
				JSONObject jObj = new JSONObject(request);
				String error = jObj.optString("error");
				if ("Could not authenticate you.".equals(error)) {
					throw new ConnectionAuthenticationException(error);
				}
			} catch (JSONException e1) {
				throw new ConnectionException(e);
			}
		}
		return jArr;
	}

	/**
	 * Get the user's own and friends timeline.
	 * 
	 * Returns the 100 most recent sent messages for the authenticating user.
	 * 
	 * @return JSONArray
	 * @throws ConnectionException 
	 * @throws ConnectionAuthenticationException 
	 * @throws ConnectionUnavailableException 
	 * @throws SocketTimeoutException 
	 */
	public JSONArray getSentDirectMessages() throws ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		String url = DIRECT_MESSAGES_SENT_URL;
		url += "?count=" + mLimit;
		if (mSinceId > 0) {
			url += "&since_id=" + mSinceId;
		}
		JSONArray jArr = null;
		String request = getRequest(url);
		try {
			jArr = new JSONArray(request);
		} catch (JSONException e) {
			try {
				JSONObject jObj = new JSONObject(request);
				String error = jObj.optString("error");
				if ("Could not authenticate you.".equals(error)) {
					throw new ConnectionAuthenticationException(error);
				}
			} catch (JSONException e1) {
				throw new ConnectionException(e);
			}
		}
		return jArr;
	}

	/**
	 * Update user status by posting to the Twitter REST API.
	 * 
	 * Updates the authenticating user's status. Requires the status parameter
	 * specified. Request must be a POST. A status update with text identical to
	 * the authenticating user's current status will be ignored.
	 * 
	 * @param message
	 * @return JSONObject
	 * @throws UnsupportedEncodingException
	 * @throws ConnectionException 
	 * @throws ConnectionAuthenticationException 
	 * @throws ConnectionUnavailableException 
	 * @throws SocketTimeoutException 
	 */
	public JSONObject updateStatus(String message, long inReplyToId) throws UnsupportedEncodingException, ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		return updateStatus(message, inReplyToId, null);
	}

	/**
	 * Update user status by posting to the Twitter REST API.
	 * 
	 * Updates the authenticating user's status. Requires the status parameter
	 * specified. Request must be a POST. A status update with text identical to
	 * the authenticating user's current status will be ignored.
	 * 
	 * @param message
	 * @return JSONObject
	 * @throws UnsupportedEncodingException
	 * @throws ConnectionException 
	 * @throws ConnectionAuthenticationException 
	 * @throws ConnectionUnavailableException 
	 * @throws SocketTimeoutException 
	 */
	public JSONObject updateStatus(String message, long inReplyToId, Location location) throws UnsupportedEncodingException, ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		String url = STATUSES_UPDATE_URL;
		List<NameValuePair> formParams = new ArrayList<NameValuePair>();
		formParams.add(new BasicNameValuePair("status", message));
		formParams.add(new BasicNameValuePair("source", SOURCE_PARAMETER));
		if (inReplyToId > 0) {
			formParams.add(new BasicNameValuePair("in_reply_to_status_id", String.valueOf(inReplyToId)));
		}
		if (location != null) {
			formParams.add(new BasicNameValuePair("lat", Location.convert(location.getLatitude(), Location.FORMAT_DEGREES)));
			formParams.add(new BasicNameValuePair("long", Location.convert(location.getLongitude(), Location.FORMAT_DEGREES)));
		}
		JSONObject jObj = null;
		try {
			jObj = new JSONObject(postRequest(url, new UrlEncodedFormEntity(formParams, HTTP.UTF_8)));
			String error = jObj.optString("error");
			if ("Could not authenticate you.".equals(error)) {
				throw new ConnectionAuthenticationException(error);
			}
		} catch (JSONException e) {
			throw new ConnectionException(e);
		}
		return jObj;
	}

	/**
	 * Destroys the status specified by the required ID parameter.
	 * The authenticating user must be the author of the specified status.
	 * 
	 * @param statusId
	 * @return JSONObject
	 * @throws UnsupportedEncodingException
	 * @throws ConnectionException
	 * @throws ConnectionAuthenticationException
	 * @throws ConnectionUnavailableException
	 * @throws SocketTimeoutException
	 */
	public JSONObject destroyStatus(long statusId) throws UnsupportedEncodingException, ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		StringBuilder url = new StringBuilder(STATUSES_DESTROY_URL);
		url.append(String.valueOf(statusId));
		url.append(EXTENSION);
		JSONObject jObj = null;
		try {
			jObj = new JSONObject(postRequest(url.toString()));
			String error = jObj.optString("error");
			if ("Could not authenticate you.".equals(error)) {
				throw new ConnectionAuthenticationException(error);
			}
		} catch (JSONException e) {
			throw new ConnectionException(e);
		}
		return jObj;
	}

	public JSONObject retweetStatus(long statusId) throws UnsupportedEncodingException, ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		StringBuilder url = new StringBuilder(STATUSES_RETWEET_URL);
		url.append(String.valueOf(statusId));
		url.append(EXTENSION);
		JSONObject jObj = null;
		try {
			jObj = new JSONObject(postRequest(url.toString()));
			String error = jObj.optString("error");
			if ("Could not authenticate you.".equals(error)) {
				throw new ConnectionAuthenticationException(error);
			}
		} catch (JSONException e) {
			throw new ConnectionException(e);
		}
		return jObj;
	}

	/**
	 * Favorites the status specified in the ID parameter as the authenticating user.
	 * Returns the favorite status when successful.
	 * 
	 * @param statusId
	 * @return JSONObject
	 * @throws UnsupportedEncodingException
	 * @throws ConnectionException
	 * @throws ConnectionAuthenticationException
	 * @throws ConnectionUnavailableException
	 * @throws SocketTimeoutException
	 */
	public JSONObject createFavorite(long statusId) throws UnsupportedEncodingException, ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		StringBuilder url = new StringBuilder(FAVORITES_CREATE_URL);
		url.append(String.valueOf(statusId));
		url.append(EXTENSION);
		JSONObject jObj = null;
		try {
			jObj = new JSONObject(postRequest(url.toString()));
			String error = jObj.optString("error");
			if ("Could not authenticate you.".equals(error)) {
				throw new ConnectionAuthenticationException(error);
			}
		} catch (JSONException e) {
			throw new ConnectionException(e);
		}
		return jObj;
	}

	/**
	 * 
	 * @param statusId
	 * @return JSONObject
	 * @throws UnsupportedEncodingException
	 * @throws ConnectionException
	 * @throws ConnectionAuthenticationException
	 * @throws ConnectionUnavailableException
	 * @throws SocketTimeoutException
	 */
	public JSONObject destroyFavorite(long statusId) throws UnsupportedEncodingException, ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		StringBuilder url = new StringBuilder(FAVORITES_DESTROY_URL);
		url.append(String.valueOf(statusId));
		url.append(EXTENSION);
		JSONObject jObj = null;
		try {
			jObj = new JSONObject(postRequest(url.toString()));
			String error = jObj.optString("error");
			if ("Could not authenticate you.".equals(error)) {
				throw new ConnectionAuthenticationException(error);
			}
		} catch (JSONException e) {
			throw new ConnectionException(e);
		}
		return jObj;
	}

	/**
	 * Verify the user's credentials.
	 * 
	 * Returns an HTTP 200 OK response code and a representation of the
	 * requesting user if authentication was successful; returns a 401 status
	 * code and an error message if not.
	 * 
	 * @return JSONObject
	 * @throws JSONException
	 * @throws ConnectionException 
	 * @throws ConnectionUnavailableException 
	 * @throws ConnectionAuthenticationException 
	 * @throws SocketTimeoutException 
	 */
	public JSONObject verifyCredentials() throws JSONException, ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		return new JSONObject(getRequest(ACCOUNT_VERIFY_CREDENTIALS_URL, new DefaultHttpClient(getParams())));
	}

	/**
	 * Check API requests status.
	 * 
	 * Returns the remaining number of API requests available to the requesting 
	 * user before the API limit is reached for the current hour. Calls to 
	 * rate_limit_status do not count against the rate limit.  If authentication 
	 * credentials are provided, the rate limit status for the authenticating 
	 * user is returned.  Otherwise, the rate limit status for the requester's 
	 * IP address is returned.
	 * 
	 * @return JSONObject
	 * @throws JSONException
	 * @throws ConnectionException
	 * @throws ConnectionAuthenticationException
	 * @throws ConnectionUnavailableException
	 * @throws SocketTimeoutException 
	 */
	public JSONObject rateLimitStatus() throws JSONException, ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		return new JSONObject(getRequest(ACCOUNT_RATE_LIMIT_STATUS_URL, new DefaultHttpClient(getParams())));
	}

	public JSONObject postTwitgoo(String uploadUrl) throws ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		String url = "http://twitgoo.com/api/upload";
		//url = "http://192.168.254.33:8080/text/twitpic/api/uploadAndPost";
		String result = null;
		JSONObject jo = null;
		StatusLine statusLine = null;
		HttpResponse response = null;
		Log.d(TAG, "postTwitgoo("+url+")");

		HttpClient client = new DefaultHttpClient(getParams());
		HttpPost method = new HttpPost(url);
		try {
			method.setHeader("User-Agent", USER_AGENT);
			method.setHeader("Accept-Encoding", "gzip");

			MultipartEntity entity = new MultipartEntity();
			String extension = JSON;

			if (mUseOAuth) {
				method.setHeader("X-Verify-Credentials-Authorization", getOAuthEchoHeader(extension));
				method.setHeader("X-Auth-Service-Provider", mTwitterGateway.replace("http://", "https://")+ACCOUNT_VERIFY_CREDENTIALS+extension);
				Log.d(TAG, "signed "+method.getFirstHeader("X-Verify-Credentials-Authorization"));
				Log.d(TAG, "signed "+method.getFirstHeader("X-Auth-Service-Provider"));
				Log.d(TAG, "test "+getOAuthEchoHeader(extension));
			} else {
				entity.addPart("username", new StringBody(mUsername, Charset.forName("UTF-8")));
				entity.addPart("password", new StringBody(mPassword, Charset.forName("UTF-8")));
			}

			entity.addPart("upload_url", new StringBody(uploadUrl, Charset.forName("UTF-8")));
			method.setEntity(entity);
			//method.setHeader("Content-Length", String.valueOf(entity.getContentLength()));
			Log.d(TAG, "length "+entity.getContentLength());
			response = client.execute(method);
			statusLine = response.getStatusLine();
			Log.d(TAG, "statuscode "+statusLine.getStatusCode());
			result = retrieveInputStream(response.getEntity(), response.getFirstHeader("Content-Encoding"));
			Log.d(TAG, "result "+result.toString());
			try {
				jo = new JSONObject(result);
			} catch (JSONException je) {
				jo = new JSONObject();
				Pattern p = Pattern.compile("<mediaurl>([^<]+)</mediaurl>", Pattern.DOTALL);
				Matcher m = p.matcher(result);
				if (m.find()) {
					jo.put("mediaurl", m.group(1));
				} else {
					p = Pattern.compile("<err msg=\"([^\"]+)\" />", Pattern.DOTALL);
					m = p.matcher(result);
					if (m.find()) {
						jo.put("code", "unknown");
						jo.put("error", m.group(1));
					}
				}
			}
		} catch (SocketTimeoutException e) {
			if (statusLine == null) {
				throw e;
			}
			Log.w(TAG, "got 200 but timeout");
		} catch (Exception e) {
			Log.e(TAG, e.getMessage() + e.toString());
			throw new ConnectionException(e);
		} finally {
			method.abort();
		}
		parseStatusCode(method, response, statusLine, url);
		return jo;
	}

	public JSONObject postYfrog(String uploadUrl) throws ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		String url = "http://yfrog.com/api/xauth_upload";
		//url = "http://192.168.254.33:8080/text/twitpic/api/uploadAndPost";
		String result = null;
		JSONObject jo = null;
		StatusLine statusLine = null;
		HttpResponse response = null;
		Log.d(TAG, "postYfrog("+url+")");

		HttpClient client = new DefaultHttpClient(getParams());
		HttpPost method = new HttpPost(url);
		try {
			method.setHeader("User-Agent", USER_AGENT);
			method.setHeader("Accept-Encoding", "gzip");

			MultipartEntity entity = new MultipartEntity();
			String extension = XML;

			if (mUseOAuth) {
				method.setHeader("X-Verify-Credentials-Authorization", getOAuthEchoHeader(extension));
				method.setHeader("X-Auth-Service-Provider", mTwitterGateway.replace("http://", "https://")+ACCOUNT_VERIFY_CREDENTIALS+extension);
				Log.d(TAG, "signed "+method.getFirstHeader("X-Verify-Credentials-Authorization"));
				Log.d(TAG, "signed "+method.getFirstHeader("X-Auth-Service-Provider"));
				Log.d(TAG, "test "+getOAuthEchoHeader(extension));
			} else {
				entity.addPart("username", new StringBody(mUsername, Charset.forName("UTF-8")));
				entity.addPart("password", new StringBody(mPassword, Charset.forName("UTF-8")));
			}

			entity.addPart("url", new StringBody(uploadUrl, Charset.forName("UTF-8")));
			entity.addPart("key", new StringBody(PrivateData.IMAGESHACK_API_KEY, Charset.forName("UTF-8")));

			method.setEntity(entity);
			//method.setHeader("Content-Length", String.valueOf(entity.getContentLength()));
			Log.d(TAG, "length "+entity.getContentLength());
			response = client.execute(method);
			statusLine = response.getStatusLine();
			Log.d(TAG, "statuscode "+statusLine.getStatusCode());
			result = retrieveInputStream(response.getEntity(), response.getFirstHeader("Content-Encoding"));
			Log.d(TAG, "result "+result.toString());
			try {
				jo = new JSONObject(result);
			} catch (JSONException je) {
				jo = new JSONObject();
				Pattern p = Pattern.compile("<mediaurl>([^<]+)</mediaurl>", Pattern.DOTALL);
				Matcher m = p.matcher(result);
				if (m.find()) {
					jo.put("mediaurl", m.group(1));
				} else {
					p = Pattern.compile("<err code=\"(\\d+)\" msg=\"([^\"]+)\" />", Pattern.DOTALL);
					m = p.matcher(result);
					if (m.find()) {
						jo.put("code", m.group(1));
						jo.put("error", m.group(2));
					}
				}
			}
		} catch (SocketTimeoutException e) {
			if (statusLine == null) {
				throw e;
			}
			Log.w(TAG, "got 200 but timeout");
		} catch (Exception e) {
			Log.e(TAG, e.getMessage() + e.toString());
			throw new ConnectionException(e);
		} finally {
			method.abort();
		}
		parseStatusCode(method, response, statusLine, url);
		return jo;
	}

	public JSONObject postTwitPic(InputStream is, String mimeType, String filename, String message) throws ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		String url = TWITPIC_PICTURE_URL;
		//url = "http://192.168.254.33:8080/text/twitpic/api/uploadAndPost";
		String result = null;
		JSONObject jo = null;
		StatusLine statusLine = null;
		HttpResponse response = null;
		Log.d(TAG, "postTwitPic("+mPictureGateway+url+")");

		HttpClient client = new DefaultHttpClient(getParams());
		HttpPost method = new HttpPost(mPictureGateway+url);
		try {
			method.setHeader("User-Agent", USER_AGENT);
			method.setHeader("Accept-Encoding", "gzip");

			InputStreamEntity ise = new InputStreamEntity(is, -1L);
			byte[] data = EntityUtils.toByteArray(ise);

			MultipartEntity entity = new MultipartEntity();
			entity.addPart("media", new ByteArrayBody(data, mimeType, filename));
			String extension = JSON;
			if (mPictureGateway.indexOf("twitpic") >= 0) {
				extension = JSON;
			} else if (mPictureGateway.indexOf("yfrog") >= 0) {
				extension = XML;
			}

			if (mUseOAuth) {
				method.setHeader("X-Verify-Credentials-Authorization", getOAuthEchoHeader(extension));
				method.setHeader("X-Auth-Service-Provider", mTwitterGateway.replace("http://", "https://")+ACCOUNT_VERIFY_CREDENTIALS+extension);
				Log.d(TAG, "signed "+method.getFirstHeader("X-Verify-Credentials-Authorization"));
				Log.d(TAG, "signed "+method.getFirstHeader("X-Auth-Service-Provider"));
				Log.d(TAG, "test "+getOAuthEchoHeader(extension));
			} else {
				entity.addPart("username", new StringBody(mUsername, Charset.forName("UTF-8")));
				entity.addPart("password", new StringBody(mPassword, Charset.forName("UTF-8")));
			}

			if (mPictureGateway.indexOf("twitpic") >= 0) {
				entity.addPart("key", new StringBody(PrivateData.TWITPIC_API_KEY, Charset.forName("UTF-8")));
			} else if (mPictureGateway.indexOf("yfrog") >= 0) {
				entity.addPart("key", new StringBody(PrivateData.IMAGESHACK_API_KEY, Charset.forName("UTF-8")));
			}

			entity.addPart("message", new StringBody(message, Charset.forName("UTF-8")));
			method.setEntity(entity);
			//method.setHeader("Content-Length", String.valueOf(entity.getContentLength()));
			Log.d(TAG, "length "+entity.getContentLength());
			response = client.execute(method);
			statusLine = response.getStatusLine();
			Log.d(TAG, "statuscode "+statusLine.getStatusCode());
			result = retrieveInputStream(response.getEntity(), response.getFirstHeader("Content-Encoding"));
			Log.d(TAG, "result "+result.toString());
			try {
				jo = new JSONObject(result);
			} catch (JSONException je) {
				jo = new JSONObject();
				Pattern p = Pattern.compile("<mediaurl>([^<]+)</mediaurl>", Pattern.DOTALL);
				Matcher m = p.matcher(result);
				if (m.find()) {
					jo.put("mediaurl", m.group(1));
				} else {
					p = Pattern.compile("<err code=\"(\\d+)\" msg=\"([^\"]+)\" />", Pattern.DOTALL);
					m = p.matcher(result);
					if (m.find()) {
						jo.put("code", m.group(1));
						jo.put("error", m.group(2));
					}
				}
			}
		} catch (SocketTimeoutException e) {
			if (statusLine == null) {
				throw e;
			}
			Log.w(TAG, "got 200 but timeout");
		} catch (Exception e) {
			Log.e(TAG, e.getMessage() + e.toString());
			throw new ConnectionException(e);
		} finally {
			method.abort();
		}
		parseStatusCode(method, response, statusLine, url);
		return jo;
	}

	public JSONObject postTweetPhoto(InputStream is, String mimeType, String filename, String message, Location location) throws ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		String url = TWEETPHOTO_PICTURE_URL;
		String result = null;
		JSONObject jo = null;
		StatusLine statusLine = null;
		HttpResponse response = null;
		Log.d(TAG, "postTweetPhoto("+mPictureGateway+url+")");

		HttpClient client = new DefaultHttpClient(getParams());
		HttpPost method = new HttpPost(mPictureGateway+url);
		try {
			method.setHeader("User-Agent", USER_AGENT);
			method.setHeader("Accept-Encoding", "gzip");

			InputStreamEntity ise = new InputStreamEntity(is, -1L);
			byte[] data = EntityUtils.toByteArray(ise);

			MultipartEntity entity = new MultipartEntity();
			entity.addPart("media", new ByteArrayBody(data, mimeType, filename));
			Log.d(TAG, "use oauth "+mUseOAuth);
			if (mUseOAuth) {
				/*
				method.setHeader("Authorization", "OAuthEcho "+getOAuthEchoQuery());
				Log.d(TAG, "signed "+method.getFirstHeader("Authorization"));
				*/
				method.setHeader("X-Verify-Credentials-Authorization", getOAuthEchoHeader(XML));
				method.setHeader("X-Auth-Service-Provider", mTwitterGateway.replace("http://", "https://")+ACCOUNT_VERIFY_CREDENTIALS+XML);
				Log.d(TAG, "signed "+method.getFirstHeader("X-Verify-Credentials-Authorization"));
				Log.d(TAG, "signed "+method.getFirstHeader("X-Auth-Service-Provider"));
				/*
				entity.addPart("isoauth", new StringBody("true", Charset.forName("UTF-8")));
				entity.addPart("username", new StringBody(mOAuthAccessToken, Charset.forName("UTF-8")));
				entity.addPart("password", new StringBody(mOAuthTokenSecret, Charset.forName("UTF-8")));
				*/
			} else {
				entity.addPart("username", new StringBody(mUsername, Charset.forName("UTF-8")));
				entity.addPart("password", new StringBody(mPassword, Charset.forName("UTF-8")));
			}
			entity.addPart("message", new StringBody(message, Charset.forName("UTF-8")));
			if (location != null) {
				entity.addPart("latitude", new StringBody(Location.convert(location.getLatitude(), Location.FORMAT_DEGREES), Charset.forName("UTF-8")));
				entity.addPart("longitude", new StringBody(Location.convert(location.getLongitude(), Location.FORMAT_DEGREES), Charset.forName("UTF-8")));
			}
			entity.addPart("post_to_twitter", new StringBody("False", Charset.forName("UTF-8")));
			entity.addPart("response_format", new StringBody("JSON", Charset.forName("UTF-8")));
			entity.addPart("api_key", new StringBody(PrivateData.TWEETPHOTO_API_KEY, Charset.forName("UTF-8")));
			method.setEntity(entity);
			//method.setHeader("Content-Length", String.valueOf(entity.getContentLength()));
			Log.d(TAG, "length "+entity.getContentLength());
			response = client.execute(method);
			statusLine = response.getStatusLine();
			Log.d(TAG, "statuscode "+statusLine.getStatusCode());
			result = retrieveInputStream(response.getEntity(), response.getFirstHeader("Content-Encoding"));
			Log.d(TAG, "result "+result.toString());
			if (statusLine.getStatusCode() == 200 || statusLine.getStatusCode() == 201) {
				jo = new JSONObject(result);
			}
		} catch (SocketTimeoutException e) {
			if (statusLine == null) {
				throw e;
			}
			Log.w(TAG, "got 200 but timeout");
		} catch (Exception e) {
			Log.e(TAG, e.getMessage() + e.toString());
			throw new ConnectionException(e);
		} finally {
			method.abort();
		}
		parseStatusCode(method, response, statusLine, url);
		return jo;
	}

	/**
	 * Execute a GET request against the Twitter REST API.
	 * 
	 * @param url
	 * @return String
	 * @throws ConnectionException 
	 * @throws ConnectionUnavailableException 
	 * @throws ConnectionAuthenticationException 
	 * @throws SocketTimeoutException 
	 */
	protected String getRequest(String url) throws ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		return getRequest(url, new DefaultHttpClient(getParams()));
	}

	/**
	 * Execute a GET request against the Twitter REST API.
	 * 
	 * @param url
	 * @param client
	 * @return String
	 * @throws ConnectionException
	 * @throws ConnectionUnavailableException 
	 * @throws ConnectionAuthenticationException 
	 * @throws SocketTimeoutException 
	 */
	protected String getRequest(String url, HttpClient client) throws ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		String result = null;
		StatusLine statusLine = null;
		HttpResponse httpResponse = null;
		Log.d(TAG, "getRequest("+mTwitterGateway+url+")");
		HttpGet getMethod = new HttpGet(mTwitterGateway+url);
		try {
			getMethod.setHeader("User-Agent", USER_AGENT);
			getMethod.setHeader("Accept-Encoding", "gzip");
			if (mUseOAuth) {
				mOAuthConsumer.sign(getMethod);
				Log.d(TAG, "signed");
			} else {
				getMethod.addHeader("Authorization", "Basic " + getCredentials());
			}

			httpResponse = client.execute(getMethod);
			statusLine = httpResponse.getStatusLine();
			Log.d(TAG, "statuscode "+statusLine.getStatusCode());
			result = retrieveInputStream(httpResponse.getEntity(), httpResponse.getFirstHeader("Content-Encoding"));
		} catch (SocketTimeoutException e) {
			if (statusLine == null) {
				throw e;
			}
			Log.w(TAG, "got 200 but timeout");
		} catch (Exception e) {
			Log.e(TAG, e.getMessage() + e.toString());
			throw new ConnectionException(e);
		} finally {
			getMethod.abort();
		}
		parseStatusCode(getMethod, httpResponse, statusLine, url);
		return result;
	}

	/**
	 * Execute a POST request against the Twitter REST API.
	 * 
	 * @param url
	 * @return String
	 * @throws ConnectionException 
	 * @throws ConnectionUnavailableException 
	 * @throws ConnectionAuthenticationException 
	 * @throws SocketTimeoutException 
	 */
	protected String postRequest(String url) throws ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		return postRequest(url, new DefaultHttpClient(getParams()), null);
	}

	/**
	 * Execute a POST request against the Twitter REST API.
	 * 
	 * @param url
	 * @return String
	 * @throws ConnectionException 
	 * @throws ConnectionUnavailableException 
	 * @throws ConnectionAuthenticationException 
	 * @throws SocketTimeoutException 
	 */
	protected String postRequest(String url, UrlEncodedFormEntity formParams) throws ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		return postRequest(url, new DefaultHttpClient(getParams()), formParams);
	}

	/**
	 * Execute a POST request against the Twitter REST API.
	 * 
	 * @param url
	 * @param client
	 * @return String
	 * @throws ConnectionException
	 * @throws ConnectionUnavailableException 
	 * @throws ConnectionAuthenticationException 
	 */
	protected String postRequest(String url, HttpClient client, UrlEncodedFormEntity formParams) throws ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException, SocketTimeoutException {
		String result = null;
		StatusLine statusLine = null;
		HttpResponse httpResponse = null;
		Log.d(TAG, "postRequest("+mTwitterGateway+url+")");
		HttpPost postMethod = new HttpPost(mTwitterGateway+url);
		try {
			postMethod.setHeader("User-Agent", USER_AGENT);
			postMethod.setHeader("Accept-Encoding", "gzip");
			if (formParams != null) {
				postMethod.setEntity(formParams);
			}
			if (mUseOAuth) {
				mOAuthConsumer.sign(postMethod);
				Log.d(TAG, "signed");
			} else {
				postMethod.addHeader("Authorization", "Basic " + getCredentials());
			}

			httpResponse = client.execute(postMethod);
			statusLine = httpResponse.getStatusLine();
			Log.d(TAG, "statuscode "+statusLine.getStatusCode());
			result = retrieveInputStream(httpResponse.getEntity(), httpResponse.getFirstHeader("Content-Encoding"));
		} catch (SocketTimeoutException e) {
			if (statusLine == null) {
				throw e;
			}
			Log.w(TAG, "got 200 but timeout");
		} catch (Exception e) {
			Log.e(TAG, e.getMessage() + e.toString());
			throw new ConnectionException(e);
		} finally {
			postMethod.abort();
		}
		parseStatusCode(postMethod, httpResponse, statusLine, url);
		return result;
	}

	/**
	 * Retrieve the input stream from the HTTP connection.
	 * 
	 * @param httpEntity
	 * @return String
	 */
	protected String retrieveInputStream(HttpEntity unbufferedHttpEntity, Header encoding) {
		int length = (int) unbufferedHttpEntity.getContentLength();
		StringBuffer stringBuffer;

		if (length > 0) {
			stringBuffer = new StringBuffer(length);
		} else {
			stringBuffer = new StringBuffer();
			Log.e(TAG, "missing content-length response!");
		}

		try {
			HttpEntity httpEntity = new BufferedHttpEntity(unbufferedHttpEntity);
			InputStream is = httpEntity.getContent();
			if (encoding != null && encoding.getValue().equalsIgnoreCase("gzip")) {
				Log.d(TAG, "encoding: gzip "+length);
				is = new GZIPInputStream(is);
			}
			InputStreamReader inputStreamReader = new InputStreamReader(is, HTTP.UTF_8);
			char buffer[] = new char[BUFFER_SIZE];
			int count;
			while ((count = inputStreamReader.read(buffer, 0, BUFFER_SIZE)) > 0) {
				stringBuffer.append(buffer, 0, count);
			}
		} catch (UnsupportedEncodingException e) {
			Log.e(TAG, e.getMessage());
		} catch (IllegalStateException e) {
			Log.e(TAG, e.getMessage());
		} catch (IOException e) {
			Log.e(TAG, e.getMessage());
		}
		return stringBuffer.toString();
	}

	/**
	 * Get the HTTP digest authentication. Uses Base64 to encode credentials.
	 * 
	 * @return String
	 */
	protected String getCredentials() {
		return new String(Base64.encodeBytes((mUsername + ":" + mPassword).getBytes()));
	}

	/**
	 * Parse the status code and throw appropriate exceptions when necessary.
	 * 
	 * @param code
	 * @param path
	 * @throws ConnectionException
	 * @throws ConnectionAuthenticationException
	 * @throws ConnectionUnavailableException
	 */
	protected void parseStatusCode(HttpRequest req, HttpResponse res, StatusLine line, String path) throws ConnectionException, ConnectionAuthenticationException, ConnectionUnavailableException {
		switch (line.getStatusCode()) {
		case 200:
		case 304:
			break;
		case 401:
			Log.d(TAG, "request");
			dumpHeaders(req);
			Log.d(TAG, "response");
			dumpHeaders(res);
			throw new ConnectionAuthenticationException(line.getReasonPhrase());
		case 400:
		case 403:
		case 404:
			throw new ConnectionException(line.getReasonPhrase());
		case 500:
		case 502:
		case 503:
			throw new ConnectionUnavailableException(line.getReasonPhrase());
		}
	}

	/**
	 * Format a Date in the twitter date format.
	 * 
	 * @param time
	 * @return
	 */
	protected String getTwitterDate(long time) {
		synchronized (mDateFormat) {
			return mDateFormat.format(new Date(time));
		}
	}

	protected HttpParams getParams() {
		HttpParams params = new BasicHttpParams();
		HttpProtocolParams.setUseExpectContinue(params, false);
		HttpConnectionParams.setConnectionTimeout(params, DEFAULT_CONNECTION_TIMEOUT);
		HttpConnectionParams.setSoTimeout(params, DEFAULT_SO_TIMEOUT);
		return params;
	}

	protected void dumpHeaders(HttpMessage res) {
		Header[] headers = res.getAllHeaders();
		for(int i=0; i<headers.length; i++) {
			Log.d(TAG, headers[i].getName()+": "+headers[i].getValue());
		}
	}

	protected String getOAuthEchoQuery(String extension) throws Exception {
			return mOAuthEcho.sign(mTwitterGateway.replace("http://", "https://")+ACCOUNT_VERIFY_CREDENTIALS+extension);
	}

	protected String getOAuthEchoHeader(String extension) throws Exception {
		HttpParameters params= new HttpParameters();
		params.put("realm", "http://"+new URI(mTwitterGateway).getHost()+"/");

		mOAuthEcho.setSigningStrategy(new AuthorizationHeaderSigningStrategy());
		mOAuthEcho.setAdditionalParameters(params);

		HttpGet get = new HttpGet(mTwitterGateway.replace("http://", "https://")+ACCOUNT_VERIFY_CREDENTIALS+extension);
		mOAuthEcho.sign(get);

		return get.getFirstHeader("Authorization").getValue();
	}
}
